package com.dd.utils.file

import android.content.Context
import android.os.Environment
import android.os.Process
import android.os.StatFs
import android.util.Log
import com.dd.utils.file.FileUtil.getSandBoxDir
import com.google.gson.Gson
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.ObservableOnSubscribe
import io.reactivex.rxjava3.core.ObservableTransformer
import io.reactivex.rxjava3.schedulers.Schedulers
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileReader
import java.io.FileWriter
import java.io.IOException
import java.io.InputStream
import java.util.Locale

const val DATA_CACHE = "DataCache"

class DataCache private constructor() {
    private val MAX_SIZE = 1024 * 1024 * 500L // 500 mb
    private val MAX_COUNT = Int.MAX_VALUE // 不限制存放数据的数量
    private val _TAG = "DataCache"

    private val mInstanceMap = mutableMapOf<String, DataCache>()
    private var mMMKVManager: DataCacheMMKVManager? = null
    private var mCache: DataCacheManager? = null

    companion object {
        private var instance: DataCache? = null
        fun getInstance(): DataCache {
            return instance ?: synchronized(this) {
                instance ?: DataCache().apply { instance = this }
            }
        }
    }

    fun get(context: Context): DataCache {
        return get(context, DATA_CACHE)
    }

    fun get(context: Context, cacheName: String = DATA_CACHE): DataCache {
        getSandBoxDir(cacheName, context)?.let {
            val f: File = it
            return get(f, MAX_SIZE, MAX_COUNT, context)
        }
        return this
    }

    fun get(context: Context, max_size: Long, max_count: Int): DataCache {
        val file = File(context.cacheDir, DATA_CACHE)
        return get(file, max_size, max_count, context)
    }

    fun get(
        cacheDir: File,
        max_size: Long,
        max_count: Int,
        context: Context
    ): DataCache {
        var manager: DataCache? = mInstanceMap[cacheDir.absoluteFile.toString() + myPid()]
        if (manager == null) {
            manager = getInstance().build(cacheDir, max_size, max_count, context)
            mInstanceMap[cacheDir.absolutePath + myPid()] = manager
        }
        return manager
    }

    private fun build(
        cacheDir: File,
        max_size: Long,
        max_count: Int,
        context: Context
    ): DataCache {
        if (!cacheDir.exists() && !cacheDir.mkdirs()) {
            throw RuntimeException("can't make dirs in " + cacheDir.absolutePath)
        }
        mMMKVManager = DataCacheMMKVManager()
        mCache = DataCacheManager(cacheDir, max_size, max_count, context, mMMKVManager)
        return this
    }

    private fun myPid(): String {
        return "_" + Process.myPid()
    }

    //********************************************************************************************//
    // =======================================
    // ============ String数据 读写 ============
    // =======================================
    //********************************************************************************************//
    /**
     * 保存 String数据 到 缓存中
     *
     * @param key   保存的key
     * @param value 保存的String数据
     */
    fun put(key: String, value: String) {
        Observable.create(ObservableOnSubscribe<Boolean> { _ ->
            mCache?.let {
                val file = it.newFile(key)
                var out: BufferedWriter? = null
                try {
                    out = BufferedWriter(FileWriter(file), 1024)
                    out.write(value)
                } catch (e: IOException) {
                    e.printStackTrace()
                } finally {
                    if (out != null) {
                        try {
                            out.flush()
                            out.close()
                        } catch (e: IOException) {
                            e.printStackTrace()
                        }
                    }
                    it.put(file)
                    Log.e(_TAG, "保存成功！")
                }
            }
        }).compose(rxSchedulerHelper<Any>())
            .subscribe()
    }

    /**
     * 保存 String数据 到 缓存中
     *
     * @param key      保存的key
     * @param value    保存的String数据
     * @param saveTime 保存的时间，单位：秒
     */
    fun put(key: String, value: String, saveTime: Int) {
        put(key, Utils.newStringWithDateInfo(saveTime, value))
    }

    /**
     * 读取 String数据
     *
     * @param key
     * @return String 数据
     */
    fun <E> getAsListObject(key: String, tClass: Class<Array<E>>?): List<E>? {
        val str = getAsString(key)
        return if (!str.isNullOrEmpty()) {
            val array = Gson().fromJson(str, tClass)
            if (array == null) null else listOf(*array)
        } else {
            null
        }
    }

    /**
     * 读取 String数据
     *
     * @param key
     * @return String 数据
     */
    fun <T> getAsObject(key: String, clazz: Class<T>): T? {
        val str = getAsString(key)
        return if (!str.isNullOrEmpty()) {
            Gson().fromJson(str, clazz)
        } else {
            null
        }
    }


    fun getAsString(key: String): String? {
        mCache?.let { mCache ->
            val file = mCache[key]
            if (!file.exists()) {
                return null
            }

            var removeFile = false
            var bufferedReader: BufferedReader? = null
            return try {
                bufferedReader = BufferedReader(FileReader(file))
                var readString = ""
                var currentLine: String
                while (bufferedReader.readLine().also { currentLine = it } != null) {
                    readString += currentLine
                }
                if (!Utils.isDue(readString)) {
                    Utils.clearDateInfo(readString)
                } else {
                    removeFile = true
                    null
                }
            } catch (e: IOException) {
                e.printStackTrace()
                null
            } finally {
                if (bufferedReader != null) {
                    try {
                        bufferedReader.close()
                    } catch (e: IOException) {
                        e.printStackTrace()
                    }
                }
                if (removeFile) remove(key)
            }
        }
        return null
    }

    /**
     * @param key name
     * @return cache
     * @throws FileNotFoundException
     */
    @Throws(FileNotFoundException::class)
    operator fun get(key: String): InputStream? {
        mCache?.let {
            val file = it[key]
            return if (!file.exists()) {
                null
            } else {
                FileInputStream(file)
            }
        }
        return null
    }

    /**
     * 移除某个key
     *
     * @param key name
     * @return 是否移除成功
     */
    fun remove(key: String): Boolean {
        mCache?.let {
            return it.remove(key)
        }
        return false
    }

    /**
     * 清除所有数据
     */
    fun clear(): Boolean {
        mCache?.let {
            it.clear()
            return true
        }
        return false
    }

    private fun <T> rxSchedulerHelper(): ObservableTransformer<Any, Any> {
        return ObservableTransformer { observable ->
            observable.subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
        }
    }

    /**
     * 时间计算工具类
     */
    object Utils {
        private const val mSeparator = ' '

        /**
         * 判断缓存的String数据是否到期
         *
         * @param str
         * @return true：到期了 false：还没有到期
         */
        fun isDue(str: String): Boolean {
            return isDue(str.toByteArray())
        }

        /**
         * 判断缓存的byte数据是否到期
         *
         * @param data
         * @return true：到期了 false：还没有到期
         */
        private fun isDue(data: ByteArray): Boolean {
            val str = getDateInfoFromDate(data)
            if (str != null && str.size == 2) {
                var saveTimeStr = str[0]
                while (saveTimeStr.startsWith("0")) {
                    saveTimeStr = saveTimeStr.substring(1, saveTimeStr.length)
                }
                val saveTime = saveTimeStr.toLong()
                val deleteAfter = str[1].toLong()
                if (System.currentTimeMillis() > saveTime + deleteAfter * 1000) {
                    return true
                }
            }
            return false
        }

        fun newStringWithDateInfo(second: Int, strInfo: String): String {
            return createDateInfo(second) + strInfo
        }

        fun clearDateInfo(strInfo: String): String? {
            var strInfo: String? = strInfo
            if (strInfo != null && hasDateInfo(strInfo.toByteArray())) {
                strInfo = strInfo.substring(strInfo.indexOf(mSeparator) + 1, strInfo.length)
            }
            return strInfo
        }

        private fun hasDateInfo(data: ByteArray?): Boolean {
            return data != null && data.size > 15 && data[13] == '-'.code.toByte() && indexOf(
                data,
                mSeparator
            ) > 14
        }

        private fun getDateInfoFromDate(data: ByteArray): Array<String>? {
            if (hasDateInfo(data)) {
                val saveDate = String(copyOfRange(data, 0, 13))
                val deleteAfter = String(copyOfRange(data, 14, indexOf(data, mSeparator)))
                return arrayOf(saveDate, deleteAfter)
            }
            return null
        }

        private fun indexOf(data: ByteArray, c: Char): Int {
            for (i in data.indices) {
                if (data[i] == c.code.toByte()) {
                    return i
                }
            }
            return -1
        }

        private fun copyOfRange(original: ByteArray, from: Int, to: Int): ByteArray {
            val newLength = to - from
            require(newLength >= 0) { "$from > $to" }
            val copy = ByteArray(newLength)
            System.arraycopy(
                original, from, copy, 0,
                (original.size - from).coerceAtMost(newLength)
            )
            return copy
        }

        private fun createDateInfo(second: Int): String {
            var currentTime = System.currentTimeMillis().toString() + ""
            while (currentTime.length < 13) {
                currentTime = "0$currentTime"
            }
            return "$currentTime-$second$mSeparator"
        }

        fun getAllCacheDataFileName(context: Context): ArrayList<String>? {
            getSandBoxDir("DataCache", context)?.let {
                val fileList = ArrayList<String>()
                val file: File = it
                val tempList = file.listFiles()
                tempList?.let { list ->
                    Log.e("DataCache", "文件个体数为：" + list.size)
                    for (i in list.indices) {
                        if (list[i].isFile) {
                            Log.e("DataCache", "文     件：" + list[i].name)
                        }
                    }
                }
                return fileList
            } ?: return null
        }

        fun availableBlock(): Boolean {
            val statFs = StatFs(Environment.getExternalStorageDirectory().path)

            //存储块总数量
            val blockCount = statFs.blockCountLong
            //块大小
            val blockSize = statFs.blockCountLong
            //可用块数量
            val availableCount = statFs.availableBlocksLong
            //剩余块数量，注：这个包含保留块（including reserved blocks）即应用无法使用的空间
            val freeBlocks = statFs.freeBytes
            //这两个方法是直接输出总内存和可用空间，也有getFreeBytes
            //API level 18（JELLY_BEAN_MR2）引入
            val totalSize = statFs.totalBytes
            val availableSize = statFs.availableBytes
            Log.e("DataCache", "total = " + getUnit((totalSize.toString() + "").toFloat()))
            Log.e(
                "DataCache",
                "availableSize = " + getUnit((availableSize.toString() + "").toFloat())
            )
            //如果可用空间小于总空间的10%
            return availableSize < totalSize * 0.1
        }

        /**
         * 单位转换
         */
        fun getUnit(size: Float = 0F): String {
            var s = size
            var index = 0
            val units = arrayOf("B", "KB", "MB", "GB", "TB")
            while (s > 1024 && index < 4) {
                s /= 1024
                index++
            }
            return String.format(Locale.getDefault(), " %.2f %s", s, units[index])
        }
    }
}